#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Multi-repository management tool - Similar to Google repo

Requires Python 3.6+ (not compatible with Python 2)
Uses Python 3 features: type hints, f-strings, subprocess.run(), pathlib

Commands:
- repo.py init: Read default.xml and initialize repositories
- repo.py sync: Sync all repositories
- repo.py st: Show repository status
- repo.py ci -m "msg": Commit changes in all repositories
"""

import os
import sys
import json
import subprocess
import argparse
import xml.etree.ElementTree as ET
from pathlib import Path
from typing import List, Dict, Optional

# Check Python version
if sys.version_info < (3, 6):
    print("Error: This script requires Python 3.6+")
    print(f"Current version: {sys.version}")
    print("Please upgrade to Python 3.6+ or use python3 command")
    sys.exit(1)


class RepoManager:
    """Multi-repository manager"""
    
    def __init__(self, config_file: str = "default.xml"):
        self.config_file = config_file
        self.config = self._load_config()
    
    def _load_config(self) -> Dict:
        """Load XML configuration file"""
        if os.path.exists(self.config_file):
            try:
                tree = ET.parse(self.config_file)
                root = tree.getroot()
                
                # Parse remote info
                remote_elem = root.find('remote')
                remote_fetch = remote_elem.get('fetch') if remote_elem is not None else ""
                
                # Parse default info
                default_elem = root.find('default')
                default_remote = default_elem.get('remote') if default_elem is not None else "origin"
                default_revision = default_elem.get('revision') if default_elem is not None else "main"
                
                repositories = []
                for project in root.findall('project'):
                    path = project.get('path')
                    name = project.get('name')
                    remote_url = f"{remote_fetch}{name}.git"
                    
                    repositories.append({
                        "path": path,
                        "remote_url": remote_url
                    })
                
                return {"repositories": repositories}
            except Exception as e:
                print(f"Warning: Failed to load XML config: {e}")
                return {"repositories": []}
        return {"repositories": []}
    

    
    def _run_git_command(self, repo_path: str, command: List[str]) -> tuple:
        """Execute git command in specified repository path"""
        try:
            result = subprocess.run(
                ['git'] + command,
                cwd=repo_path,
                capture_output=True,
                text=True,
                encoding='utf-8'
            )
            return result.returncode, result.stdout, result.stderr
        except Exception as e:
            return 1, "", str(e)
    
    def _is_git_repo(self, path: str) -> bool:
        """Check if path is a git repository"""
        return os.path.exists(os.path.join(path, '.git'))
    
    def _get_display_name(self, path: str) -> str:
        """Get display name from path"""
        if path == "./":
            return "root"
        # Remove ./ prefix and return directory name
        clean_path = path.replace("./", "")
        return clean_path if clean_path else "root"
    
    def _colorize(self, text: str, color: str) -> str:
        """Add color to text using Google repo style colors"""
        # Google repo style color scheme with standard ANSI codes
        colors = {
            # Status colors (Google repo style)
            'red': '\033[31m',      # Errors, failures
            'green': '\033[32m',    # Success, OK status
            'yellow': '\033[33m',   # Warnings, in progress
            'blue': '\033[34m',     # Info, processing
            'magenta': '\033[35m',  # Paths, special info
            'cyan': '\033[36m',     # Headers, section titles
            'white': '\033[37m',    # Normal text emphasis
            
            # Bright variants for better visibility
            'bright_red': '\033[91m',
            'bright_green': '\033[92m',
            'bright_yellow': '\033[93m',
            'bright_blue': '\033[94m',
            'bright_magenta': '\033[95m',
            'bright_cyan': '\033[96m',
            'bright_white': '\033[97m',
            
            # Text formatting
            'bold': '\033[1m',
            'dim': '\033[2m',
            'reset': '\033[0m'
        }
        return f"{colors.get(color, '')}{text}{colors['reset']}"
    
    def init(self):
        """Initialize repository configuration"""
        print(self._colorize("Initializing repo config...", "bright_cyan"))
        
        # Check if config file exists
        if not os.path.exists(self.config_file):
            print(self._colorize(f"Error: Config file {self.config_file} not found", "bright_red"))
            return
        
        # Read XML config and initialize repositories
        print(self._colorize(f"Reading XML config: {self.config_file}", "blue"))
        
        if not self.config.get("repositories"):
            print(self._colorize("No repositories configured", "yellow"))
            return
        
        print(self._colorize(f"Found {len(self.config['repositories'])} repositories", "green"))
        
        # Initialize each repository
        for repo in self.config["repositories"]:
            path = repo["path"]
            remote_url = repo.get("remote_url", "")
            
            display_name = self._get_display_name(path)
            print(f"\n{self._colorize(f'Initializing: {display_name}', 'blue')}")
            print(f"Path: {self._colorize(path, 'magenta')}")
            
            # If path doesn't exist, create directory
            if not os.path.exists(path):
                if remote_url:
                    print(self._colorize(f"Repository not found, cloning: {remote_url}", "yellow"))
                    parent_dir = os.path.dirname(path)
                    if parent_dir and not os.path.exists(parent_dir):
                        os.makedirs(parent_dir)
                    
                    result = subprocess.run(
                        ['git', 'clone', remote_url, path],
                        capture_output=True,
                        text=True
                    )
                    
                    if result.returncode == 0:
                        print(self._colorize("✓ Clone successful", "bright_green"))
                    else:
                        print(self._colorize(f"✗ Clone failed: {result.stderr}", "bright_red"))
                else:
                    print(self._colorize("✗ Path not found and no remote URL configured", "bright_red"))
                continue
            
            # Check if it's a git repository
            if not self._is_git_repo(path):
                if remote_url:
                    print(self._colorize("Directory exists but not a git repo, trying to initialize...", "yellow"))
                    # If directory is empty, clone directly
                    if not os.listdir(path):
                        os.rmdir(path)
                        result = subprocess.run(
                            ['git', 'clone', remote_url, path],
                            capture_output=True,
                            text=True
                        )
                        if result.returncode == 0:
                            print(self._colorize("✓ Clone successful", "bright_green"))
                        else:
                            print(self._colorize(f"✗ Clone failed: {result.stderr}", "bright_red"))
                    else:
                        print(self._colorize("✗ Directory not empty and not a git repo", "bright_red"))
                else:
                    print(self._colorize("✗ Not a git repo and no remote URL configured", "bright_red"))
                continue
            
            print(self._colorize("✓ Repository exists", "bright_green"))
        
        print(self._colorize("\nRepository initialization complete", "bright_cyan"))
    

    
    def sync(self):
        """Sync all repositories"""
        print(self._colorize("Starting sync for all repositories...", "bright_cyan"))
        
        if not self.config.get("repositories"):
            print(self._colorize("Error: No repositories configured, run 'repo.py init' first", "bright_red"))
            return
        
        for repo in self.config["repositories"]:
            path = repo["path"]
            
            display_name = self._get_display_name(path)
            print(f"\n{self._colorize(f'Syncing: {display_name}', 'blue')}")
            print(f"Path: {self._colorize(path, 'magenta')}")
            
            if not os.path.exists(path):
                # Repository doesn't exist, try to clone
                if repo.get("remote_url"):
                    print(self._colorize("Repository not found, cloning...", "yellow"))
                    parent_dir = os.path.dirname(path)
                    if parent_dir and not os.path.exists(parent_dir):
                        os.makedirs(parent_dir)
                    
                    result = subprocess.run(
                        ['git', 'clone', repo["remote_url"], path],
                        capture_output=True,
                        text=True
                    )
                    
                    if result.returncode == 0:
                        print(self._colorize("✓ Clone successful", "bright_green"))
                    else:
                        print(self._colorize(f"✗ Clone failed: {result.stderr}", "bright_red"))
                else:
                    print(self._colorize("✗ Path not found and no remote URL configured", "bright_red"))
                continue
            
            if not self._is_git_repo(path):
                print(self._colorize(f"✗ {path} is not a git repository", "red"))
                continue
            
            # Get current branch
            returncode, stdout, stderr = self._run_git_command(path, ['branch', '--show-current'])
            current_branch = stdout.strip() if returncode == 0 else "unknown"
            
            # Pull latest code
            print(self._colorize(f"Current branch: {current_branch}", "magenta"))
            returncode, stdout, stderr = self._run_git_command(path, ['pull'])
            
            if returncode == 0:
                print(self._colorize("✓ Sync successful", "bright_green"))
                if stdout.strip():
                    print(f"  {self._colorize(stdout.strip(), 'white')}")
            else:
                print(self._colorize(f"✗ Sync failed: {stderr}", "bright_red"))
        
        print(self._colorize("\nAll repositories sync complete", "bright_cyan"))
    
    def status(self):
        """Show repository status (display repos with changes or unpushed commits)"""
        print(self._colorize("Checking all repository status...\n", "bright_cyan"))
        
        if not self.config.get("repositories"):
            print(self._colorize("Error: No repositories configured, run 'repo.py init' first", "bright_red"))
            return
        
        has_changes = False
        
        for repo in self.config["repositories"]:
            path = repo["path"]
            
            display_name = self._get_display_name(path)
            
            if not os.path.exists(path):
                print(self._colorize(f"Path: {path}", "blue"))
                print(self._colorize("Status: ✗ Repository path not found", "red"))
                print("-" * 50)
                has_changes = True
                continue
            
            if not self._is_git_repo(path):
                print(self._colorize(f"Path: {path}", "blue"))
                print(self._colorize("Status: ✗ Not a git repository", "red"))
                print("-" * 50)
                has_changes = True
                continue
            
            # Get current branch
            returncode, stdout, stderr = self._run_git_command(path, ['branch', '--show-current'])
            current_branch = stdout.strip() if returncode == 0 else "unknown"
            
            # Get status
            returncode, stdout, stderr = self._run_git_command(path, ['status', '--porcelain'])
            
            # Check for unpushed commits
            returncode_log, stdout_log, stderr_log = self._run_git_command(path, ['log', '--oneline', '@{u}..HEAD'])
            has_unpushed = returncode_log == 0 and stdout_log.strip()
            
            # Only show repos with changes or errors
            if returncode != 0 or stdout.strip() or has_unpushed:
                has_changes = True
                print(self._colorize(f"Path: {path}", "blue"))
                print(self._colorize(f"Branch: {current_branch}", "magenta"))
                
                if returncode == 0:
                    if stdout.strip():
                        print(self._colorize("Status: Uncommitted changes", "yellow"))
                        for line in stdout.strip().split('\n'):
                            if line.strip():
                                print(f"  {self._colorize(line, 'yellow')}")
                    else:
                        print(self._colorize("Status: ✓ Working directory clean", "bright_green"))
                else:
                    print(self._colorize(f"Status: ✗ Failed to get status: {stderr}", "bright_red"))
                
                if has_unpushed:
                    unpushed_count = len(stdout_log.strip().split('\n'))
                    print(self._colorize(f"Unpushed commits: {unpushed_count}", "cyan"))
                
                print("-" * 50)
        
        if not has_changes:
            print(self._colorize("✓ All repositories are clean, no changes to handle", "bright_green"))
    
    def push(self):
        """Push all repositories (auto pull --rebase first)"""
        print(self._colorize(f"Starting push for all repositories...\n", "bright_cyan"))
        
        if not self.config.get("repositories"):
            print(self._colorize("Error: No repositories configured, run 'repo.py init' first", "bright_red"))
            return
        
        for repo in self.config["repositories"]:
            path = repo['path']
            display_name = self._get_display_name(path)
            
            print(self._colorize(f"Processing: {display_name}", "blue"))
            
            if not os.path.exists(path):
                print(self._colorize(f"  ✗ Path not found: {path}", "red"))
                continue
            
            if not os.path.exists(os.path.join(path, '.git')):
                print(self._colorize(f"  ✗ Not a git repository: {path}", "red"))
                continue
            
            # Execute pull --rebase first
            print(self._colorize("  Executing pull --rebase...", "yellow"))
            result_pull = subprocess.run(
                ['git', 'pull', '--rebase'],
                cwd=path,
                capture_output=True,
                text=True
            )
            
            if result_pull.returncode != 0:
                print(self._colorize(f"  ✗ pull --rebase failed: {result_pull.stderr.strip()}", "bright_red"))
                continue
            else:
                print(self._colorize("  ✓ pull --rebase successful", "bright_green"))
            
            # Then execute push
            print(self._colorize("  Executing push...", "yellow"))
            result_push = subprocess.run(
                ['git', 'push'],
                cwd=path,
                capture_output=True,
                text=True
            )
            
            if result_push.returncode != 0:
                print(self._colorize(f"  ✗ push failed: {result_push.stderr.strip()}", "bright_red"))
            else:
                print(self._colorize("  ✓ push successful", "bright_green"))
            
            print("-" * 50)
        
        print(self._colorize("\nAll repositories push complete", "bright_cyan"))
    
    def commit(self, message: str):
        """Commit changes in all repositories"""
        print(self._colorize(f"Committing changes in all repositories, message: {message}\n", "bright_cyan"))
        
        if not self.config.get("repositories"):
            print(self._colorize("Error: No repositories configured, run 'repo.py init' first", "bright_red"))
            return
        
        if not message:
            print(self._colorize("Error: Commit message cannot be empty", "bright_red"))
            return
        
        for repo in self.config["repositories"]:
            path = repo["path"]
            
            display_name = self._get_display_name(path)
            print(self._colorize(f"Processing: {display_name}", "blue"))
            
            if not os.path.exists(path) or not self._is_git_repo(path):
                print(self._colorize("✗ Skipped: Path not found or not a git repository", "bright_red"))
                continue
            
            # Check for changes
            returncode, stdout, stderr = self._run_git_command(path, ['status', '--porcelain'])
            
            if returncode != 0:
                print(self._colorize(f"✗ Failed to get status: {stderr}", "bright_red"))
                continue
            
            if not stdout.strip():
                print(self._colorize("✓ No changes to commit", "bright_green"))
                continue
            
            # Add all changes
            returncode, stdout, stderr = self._run_git_command(path, ['add', '.'])
            if returncode != 0:
                print(self._colorize(f"✗ Failed to add files: {stderr}", "bright_red"))
                continue
            
            # Commit changes
            returncode, stdout, stderr = self._run_git_command(path, ['commit', '-m', message])
            if returncode == 0:
                print(self._colorize("✓ Commit successful", "bright_green"))
                if stdout.strip():
                    print(f"  {self._colorize(stdout.strip(), 'white')}")
            else:
                print(self._colorize(f"✗ Commit failed: {stderr}", "bright_red"))
            
            print()
        
        print(self._colorize("All repositories processed", "bright_cyan"))


def main():
    """Main function"""
    parser = argparse.ArgumentParser(description='Multi-repository management tool')
    parser.add_argument('--config', '-c', default='default.xml', help='XML configuration file path (default: default.xml)')
    subparsers = parser.add_subparsers(dest='command', help='Available commands')
    
    # init command
    subparsers.add_parser('init', help='Read XML config file and initialize repositories')
    
    # sync command
    subparsers.add_parser('sync', help='Sync all repositories')
    
    # st command
    subparsers.add_parser('st', help='Show repository status (display repos with changes or unpushed commits)')
    
    # ci command
    ci_parser = subparsers.add_parser('ci', help='Commit changes in all repositories')
    ci_parser.add_argument('-m', '--message', required=True, help='Commit message')
    
    # push command
    subparsers.add_parser('push', help='Push all repositories (auto pull --rebase first)')
    
    args = parser.parse_args()
    
    if not args.command:
        parser.print_help()
        return
    
    repo_manager = RepoManager(args.config)
    
    if args.command == 'init':
        repo_manager.init()
    elif args.command == 'sync':
        repo_manager.sync()
    elif args.command == 'st':
        repo_manager.status()
    elif args.command == 'push':
        repo_manager.push()
    elif args.command == 'ci':
        repo_manager.commit(args.message)
    else:
        parser.print_help()


if __name__ == '__main__':
    main()